﻿//checked: partially
using System.Collections.Generic;
using System.IO;
using net.entity.framework.common;

namespace net.entity.framework.query {
    public abstract class QueryCompiler {
        #region Members

        protected readonly QueryState queryState;

        #endregion

        #region Constructors

        protected QueryCompiler(QueryState queryState) {
            this.queryState = queryState;
        }

        #endregion

        #region Methods

        public string CompileAndRun(QueryState queryState)
        {
            var compiledState = Compile(queryState);
            return Run(compiledState);
        }

        public CompiledQueryState Compile(QueryState queryState)
        {
            //The order of compilation is bottom-up,
            //so that we build up the complete aliases
            var compiledQueryState = new CompiledQueryState(queryState);

            //append ORDERBY clauses
            if (!compiledQueryState.OrderByRoots.IsEmpty()) {
                foreach (var root in compiledQueryState.OrderByRoots) {
                    root.CompileState(compiledQueryState);
                }
            }

            //append HAVING clauses
            if (compiledQueryState.HavingClause != null) {
                compiledQueryState.HavingClause.CompileState(compiledQueryState);
            }

            //append GROUP BY clauses
            if (!compiledQueryState.GroupByRoots.IsEmpty()) {
                foreach (var root in compiledQueryState.GroupByRoots) {
                    root.CompileState(compiledQueryState);
                }
            }

            //append WHERE clauses
            if (compiledQueryState.WhereClause != null) {
                compiledQueryState.WhereClause.CompileState(compiledQueryState);
            }

            //append SELECT clauses
            if (!compiledQueryState.SelectRoots.IsEmpty()) {
                foreach (var root in compiledQueryState.SelectRoots) {
                    root.CompileState(compiledQueryState);
                }
            }

            compiledQueryState.Compile();
            //append JOIN clauses
            //if (!compiledQueryState.JoinClauses.IsEmpty()) {
            //    foreach (var joinClause in compiledQueryState.JoinClauses) {
            //        joinClause.CompileState(compiledQueryState);
            //    }
            //}
            return compiledQueryState;
        }

        public abstract string Run(CompiledQueryState compiledQueryState);

        #endregion
    }

    public sealed class SelectQueryCompiler : QueryCompiler {
        public SelectQueryCompiler(QueryState queryState) : base(queryState) {}

        public override string Run(CompiledQueryState compiledQueryState) {
            string query;
            using (var writer = new StringWriter()) {
                //append SELECT clauses
                EmitSelectClause(writer, compiledQueryState);
                //append FROM clauses
                EmitFromClause(writer, compiledQueryState);
                //append JOIN clauses
                //EmitJoins(writer);
                //append WHERE clauses
                EmitWhereClause(writer, compiledQueryState);
                //append GROUP BY clauses
                EmitGroupByClause(writer, compiledQueryState);
                //append HAVING clauses
                EmitHavingClause(writer, compiledQueryState);
                //append ORDERBY clauses
                EmitOrderByClause(writer, compiledQueryState);
                //append all the clauses in order
                query = writer.ToString();
            }
            return query;
        }

        private static void EmitOrderByClause(TextWriter writer, CompiledQueryState state) {
            EmitTerminalsHelper(writer, state.OrderByRoots, state, "ORDER BY", ",");
        }

        private static void EmitHavingClause(TextWriter writer, CompiledQueryState state) {
            if (state.HavingClause != null && state.HavingClause != BooleanConstant.True) {
                writer.Write("\nHAVING ");
                state.HavingClause.EmitQuery(writer, state);
            }
        }

        private static void EmitGroupByClause(TextWriter writer, CompiledQueryState state) {
            EmitTerminalsHelper(writer, state.GroupByRoots, state, "GROUP BY", ",");
        }

        private static void EmitWhereClause(TextWriter writer, CompiledQueryState state) {
            if (state.WhereClause != null) {
                writer.Write("\nWHERE ");
                state.WhereClause.EmitQuery(writer, state);
            }
        }

        //private void EmitJoins(TextWriter writer) {
        //    if (!compiledQueryState.JoinClauses.IsEmpty()) {
        //        foreach (var joinClause in compiledQueryState.JoinClauses)
        //        {
        //            joinClause.EmitQuery(writer, compiledQueryState);
        //        }
        //    }
        //}

        private static void EmitFromClause(TextWriter writer, CompiledQueryState state) {
            if (state.ContainsAliases) {
                writer.Write("\nFROM");
                state.EmitFromClause(writer);
            }
        }

        private static void EmitSelectClause(TextWriter writer, CompiledQueryState state) {
            writer.Write("\nSELECT ");
            if (state.Distinct) {
                writer.Write(" DISTINCT ");
            }

            if (state.Top > 0) {
                writer.Write(" TOP ({0}) ", state.Top);
            }

            if (!state.SelectRoots.IsEmpty()) {
                var index = 0;
                foreach (var root in state.SelectRoots) {
                    index++;
                    writer.Write("\t");
                    root.EmitQuery(writer, state);
                    if (index < state.SelectRoots.Length) {
                        writer.Write(",\n");
                    }
                }
            }
        }

        private static void EmitTerminalsHelper(TextWriter writer, ICollection<Terminal> terminals,
                                                CompiledQueryState state, string prefix, string seperator) {
            if (terminals.IsEmpty()) {
                return;
            }
            writer.Write("\n {0} ", prefix);
            var index = 0;
            foreach (var root in terminals) {
                index++;
                root.EmitQuery(writer, state);
                if (index < terminals.Count) {
                    writer.WriteLine(seperator);
                }
            }
        }
    }

    public sealed class DeleteQueryCompiler : QueryCompiler {
        public DeleteQueryCompiler(QueryState queryState) : base(queryState) {}

        public override string Run(CompiledQueryState compiledQueryState) {
            string query;
            using (var writer = new StringWriter()) {
                //append SELECT clauses
                EmitDeleteClause(writer, compiledQueryState);

                EmitDeleteClause(writer, compiledQueryState);

                //append WHERE clauses
                EmitWhereClause(writer, compiledQueryState);

                query = writer.ToString();
            }
            return query;
        }

        private static void EmitWhereClause(TextWriter writer, CompiledQueryState state) {
            if (state.WhereClause != null) {
                writer.Write("\nWHERE ");
                state.WhereClause.EmitQuery(writer, state);
            }
        }

        private static void EmitFromClause(TextWriter writer, CompiledQueryState state) {
            if (state.ContainsAliases) {
                writer.Write("\nFROM");
                state.EmitFromClause(writer);
                //index = 0;
                //foreach (var alias in aliases) {
                //    index++;
                //    writer.Write("\t{0}.dbo.{1} [{2}]", alias.Value.Catalogue, alias.Value.Table,
                //                 alias.Value.AliasName);
                //    if (index < aliases.Count) {
                //        writer.Write(",\n");
                //    }
                //}
            }
        }

        private static void EmitDeleteClause(TextWriter writer, CompiledQueryState state) {
            writer.Write("\nDELTE FROM ");

            if (!state.SelectRoots.IsEmpty()) {
                var index = 0;
                foreach (var root in state.SelectRoots) {
                    index++;
                    writer.Write("\t");
                    root.EmitQuery(writer, state);
                    if (index < state.SelectRoots.Length) {
                        writer.Write(",\n");
                    }
                }
            }
        }
    }
}